/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.internal.core.helper;

import com.helger.jcodemodel.AbstractJClass;
import com.helger.jcodemodel.IJExpression;
import com.helger.jcodemodel.JClassAlreadyExistsException;
import com.helger.jcodemodel.JDefinedClass;
import com.helger.jcodemodel.JFieldRef;
import com.helger.jcodemodel.JFieldVar;
import com.helger.jcodemodel.JInvocation;
import com.helger.jcodemodel.JMethod;
import com.helger.jcodemodel.JMod;
import com.helger.jcodemodel.JVar;

import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.helper.APTCodeModelHelper;
import org.ohosannotations.helper.AnnotationHelper;
import org.ohosannotations.helper.BundleHelper;
import org.ohosannotations.helper.CanonicalNameConstants;
import org.ohosannotations.helper.Constant;
import org.ohosannotations.helper.OhosManifest;
import org.ohosannotations.helper.ParcelerHelper;
import org.ohosannotations.holder.HasIntentBuilder;
import org.ohosannotations.internal.process.ProcessHolder;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

import javax.lang.model.element.Element;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import static com.helger.jcodemodel.JExpr._new;
import static com.helger.jcodemodel.JExpr._super;
import static com.helger.jcodemodel.JExpr.cast;
import static com.helger.jcodemodel.JExpr.ref;
import static com.helger.jcodemodel.JMod.PUBLIC;
import static com.helger.jcodemodel.JMod.STATIC;
import static org.ohosannotations.helper.CanonicalNameConstants.SEQUENCEABLE;
import static org.ohosannotations.helper.CanonicalNameConstants.SERIALIZABLE;
import static org.ohosannotations.helper.CanonicalNameConstants.STRING;
import static org.ohosannotations.helper.ModelConstants.generationSuffix;

/**
 * 目的构建器
 * intentBuilder
 *
 * @author dev
 * @date 2021/07/26
 */
public abstract class IntentBuilder {
    /**
     * 环境
     */
    protected OhosAnnotationsEnvironment environment;
    /**
     * 持有人
     */
    protected HasIntentBuilder holder;
    /**
     * 清单
     */
    protected OhosManifest ohosManifest;
    /**
     * 构建器类
     */
    protected JDefinedClass builderClass;
    /**
     * 上下文字段
     */
    protected JFieldRef contextField;
    /**
     * 意图领域
     */
    protected JFieldRef intentField;
    /**
     * 上下文类
     */
    protected AbstractJClass contextClass;
    /**
     * 意图类
     */
    protected AbstractJClass intentClass;
    /**
     * 元素跑龙套
     */
    protected Elements elementUtils;
    /**
     * 类型跑龙套
     */
    protected Types typeUtils;
    /**
     * 代码模型辅助
     */
    protected APTCodeModelHelper codeModelHelper;
    /**
     * 注释的助手
     */
    protected AnnotationHelper annotationHelper;
    /**
     * 包裹的助手
     */
    protected ParcelerHelper parcelerHelper;

    /**
     * 构造参数
     *
     * @param holder 持有人
     * @param ohosManifest 嗳哟清单
     */
    public IntentBuilder(HasIntentBuilder holder, OhosManifest ohosManifest) {
        this.environment = holder.getEnvironment();
        this.holder = holder;
        this.ohosManifest = ohosManifest;
        this.annotationHelper = new AnnotationHelper(environment);
        this.parcelerHelper = new ParcelerHelper(environment);
        codeModelHelper = new APTCodeModelHelper(environment);
        elementUtils = environment.getProcessingEnvironment().getElementUtils();
        typeUtils = environment.getProcessingEnvironment().getTypeUtils();
        contextClass = environment.getClasses().CONTEXT;
        intentClass = environment.getClasses().INTENT;
    }

    /**
     * 创建 build
     *
     * @throws JClassAlreadyExistsException jclass已经存在异常
     */
    public void build() throws JClassAlreadyExistsException {
        createClass();
        createContextConstructor();
        createIntentMethod();
    }

    /**
     * createClass
     *
     * @throws JClassAlreadyExistsException jclass已经存在异常
     */
    private void createClass() throws JClassAlreadyExistsException {
        builderClass = holder.getGeneratedClass()._class(PUBLIC | STATIC, "IntentBuilder" + generationSuffix());
        builderClass._extends(getSuperClass());
        holder.setIntentBuilderClass(builderClass);
        contextField = ref(Constant.CONTEXT);
        intentField = ref("intent");
    }

    /**
     * createContextConstructor
     */
    private void createContextConstructor() {
        IJExpression generatedClass = holder.getGeneratedClass().dotclass();
        JMethod constructor = holder.getIntentBuilderClass().constructor(JMod.PUBLIC);
        JVar constructorContextParam = constructor.param(getClasses().CONTEXT, Constant.CONTEXT);
        constructor.body().invoke("super").arg(constructorContextParam).arg(generatedClass);
    }

    /**
     * createIntentMethod
     */
    private void createIntentMethod() {
        JMethod method = holder.getGeneratedClass().method(STATIC | PUBLIC, holder.getIntentBuilderClass(), "intent");
        JVar contextParam = method.param(contextClass, Constant.CONTEXT);
        method.body()._return(_new(holder.getIntentBuilderClass()).arg(contextParam));
    }

    /**
     * 获取解析方法
     *
     * @param element 元素
     * @param intentExtra 额外意图
     * @return JMethod
     */
    public JMethod getPutExtraMethod(Element element, IntentExtra intentExtra) {
        return addPutExtraMethod(element, Collections.singletonList(intentExtra));
    }

    /**
     * 获取解析方法
     *
     * @param element 元素
     * @param intentExtra 额外意图
     * @return JMethod
     */

    public JMethod getPutExtraMethod(Element element, List<IntentExtra> intentExtra) {
        return addPutExtraMethod(element, intentExtra);
    }

    /**
     * 添加解析方法
     *
     * @param element 元素
     * @param intentExtras 额外意图
     * @return JMethod
     */
    private JMethod addPutExtraMethod(Element element, List<IntentExtra> intentExtras) {
        String docComment = elementUtils.getDocComment(element);

        JMethod method = holder.getIntentBuilderClass().method(PUBLIC, holder.getIntentBuilderClass(),
            element.getSimpleName().toString());
        method.javadoc().addReturn().append("the IntentBuilder to chain calls");
        codeModelHelper.addTrimmedDocComment(method, docComment);

        int paramCount = intentExtras.size();
        for (int ii = 0; ii < paramCount; ii++) {
            IntentExtra intentExtra = intentExtras.get(ii);
            method.javadoc().addParam(intentExtra.parameterName).append("the value for this extra");
            AbstractJClass parameterClass = codeModelHelper.typeMirrorToJClass(intentExtra.type);
            JVar extraParameterVar = method.param(parameterClass, intentExtra.parameterName);
            JInvocation superCall = getSuperPutExtraInvocation(intentExtra.type,
                extraParameterVar, intentExtra.keyField);
            if (paramCount == ii + 1) {
                method.body()._return(superCall);
            } else {
                method.body().add(superCall);
            }
        }
        return method;
    }

    /**
     * 获得解析调用
     *
     * @param elementType 元素类型
     * @param extraParam 附加参数
     * @param extraKeyField extraKeyField
     * @return JInvocation
     */
    public JInvocation getSuperPutExtraInvocation(TypeMirror elementType, JVar extraParam, JFieldVar extraKeyField) {
        IJExpression extraParameterArg = extraParam;
        if (elementType.getKind() == TypeKind.DECLARED) {
            // Cast to Parcelable, wrap with Parcels.wrap or cast Serializable if needed
            TypeMirror parcelableType = elementUtils.getTypeElement(SEQUENCEABLE).asType();
            if (typeUtils.isSubtype(elementType, parcelableType)) {
                TypeMirror serializableType = elementUtils.getTypeElement(SERIALIZABLE).asType();
                if (typeUtils.isSubtype(elementType, serializableType)) {
                    extraParameterArg = cast(environment.getClasses().PARCELABLE, extraParameterArg);
                }
            } else if (!BundleHelper.METHOD_SUFFIX_BY_TYPE_NAME.containsKey(elementType.toString())
                && parcelerHelper.isParcelType(elementType)) {
                extraParameterArg = environment.getJClass(CanonicalNameConstants.PARCELS_UTILITY_CLASS)
                    .staticInvoke("wrap").arg(extraParameterArg);
            } else {
                TypeMirror stringType = elementUtils.getTypeElement(STRING).asType();
                if (!typeUtils.isSubtype(elementType, stringType)) {
                    extraParameterArg = cast(environment.getClasses().SERIALIZABLE, extraParameterArg);
                }
            }
        }
        return _super().invoke("extra").arg(extraKeyField).arg(extraParameterArg);
    }

    /**
     * 获取父类Class
     *
     * @return AbstractJClass
     */
    protected abstract AbstractJClass getSuperClass();

    /**
     * 得到类
     *
     * @return {@link }
     */
    protected ProcessHolder.Classes getClasses() {
        return environment.getClasses();
    }

    /**
     * 获取Class
     *
     * @param clazz clazz
     * @return AbstractJClass
     */
    protected AbstractJClass getjClass(Class<?> clazz) {
        return environment.getJClass(clazz);
    }

    /**
     * 额外获取Intent
     *
     * @since 2021-06-18
     */
    public static class IntentExtra {
        private final TypeMirror type;
        private final String parameterName;
        private final JFieldVar keyField;

        /**
         * 构造参数
         *
         * @param type type
         * @param parameterName parameterName
         * @param keyField keyField
         */
        public IntentExtra(TypeMirror type, String parameterName, JFieldVar keyField) {
            this.type = type;
            this.parameterName = parameterName;
            this.keyField = keyField;
        }

        /**
         * getType
         *
         * @return type
         */
        public TypeMirror getType() {
            return type;
        }

        /**
         * getParameterName
         *
         * @return parameterName
         */
        public String getParameterName() {
            return parameterName;
        }

        /**
         * getKeyField
         *
         * @return keyField
         */
        public JFieldVar getKeyField() {
            return keyField;
        }

        /**
         * equals
         *
         * @param obj obj
         * @return false
         */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null || getClass() != obj.getClass()) {
                return false;
            }
            IntentExtra that = (IntentExtra) obj;
            return Objects.equals(type, that.type) && Objects.equals(parameterName,
                that.parameterName) && Objects.equals(keyField, that.keyField);
        }

        /**
         * hashCode
         *
         * @return Objects
         */
        @Override
        public int hashCode() {
            return Objects.hash(type, parameterName, keyField);
        }
    }
}
